package redis.inaction.ratelimiter.filter.metrics;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;

@Slf4j
public class MetricsFilter implements GlobalFilter, Ordered {

    private String metricsName;

    @Value("${cmRoleName:gateway}")
    private String systemRoleName;

    private final MeterRegistry meterRegistry;

    public MetricsFilter(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @PostConstruct
    public void init(){
        metricsName = systemRoleName + "_requests";
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Timer.Sample sample = Timer.start(meterRegistry);
        return chain.filter(exchange).doOnSuccess(aVoid -> endTimerRespectingCommit(exchange, sample, null))
                .doOnError(throwable -> endTimerRespectingCommit(exchange, sample, throwable));
    }

    private void endTimerRespectingCommit(ServerWebExchange exchange, Timer.Sample sample, Throwable t) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            endTimerInner(exchange, sample, t);
        }
        else {
            response.beforeCommit(() -> {
                endTimerInner(exchange, sample, t);
                return Mono.empty();
            });
        }
    }

    private void endTimerInner(ServerWebExchange exchange, Timer.Sample sample, Throwable t) {
        Tags tags = MetricsTagsSupport.buildTags(exchange, t);
        sample.stop(meterRegistry.timer(metricsName, tags));
    }

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }
}
